package br.ufpe.cin.imlm2.util.path;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;

/**
 * A simple implementation of a generic path that utilizes a list as container for the nodes.
 * 
 * @author irineu
 *
 * @param <T>
 */
public class SimplePath<T> implements Path<T> {


	private List<T> nodes;

	public SimplePath() {
		this.nodes = new ArrayList<T>();
	}

	public SimplePath<T> addPoint(T t){
		if(t == null)
			throw new IllegalArgumentException("You may not add null to a path.");
		this.nodes.add(t);
		return this;
	}

	public SimplePath<T> addPath(SimplePath<T> sp){
		for (T t : sp) {
			this.addPoint(t);
		}
		return this;
	}

	@Override
	public T getEndPoint() throws EmptyPathException {
		if(this.isEmpty())
			throw new EmptyPathException("Empty path has no ending point.");
		return this.nodes.get(this.nodes.size() - 1);
	}

	@Override
	public T getStartingPoint() throws EmptyPathException {
		if(this.isEmpty())
			throw new EmptyPathException("Empty path has no starting point.");
		return this.nodes.get(0);
	}

	@Override
	public boolean isEmpty() {
		return this.length() == 0;
	}

	@Override
	public int length() {
		return this.nodes.size();
	}

	@Override
	public Iterator<T> iterator() {
		return this.nodes.iterator();
	}

	@Override
	public boolean contains(Path<T> otherPath) {
		boolean contains = false;
		
		if(otherPath.length() == 0) {
			// A path always contains the empty path (even if it is an empty path itself).
			contains = true;
		} else if(otherPath.length() <= this.length()) {
			// If the other path length is equal or smaller than this path length we can start looking
			// for a match.
			T otherStart = otherPath.getStartingPoint();
			int otherStartInd = this.nodes.indexOf(otherStart);
			if(otherStartInd > 0) {
				ListIterator<T> listIt = this.nodes.listIterator(otherStartInd);
				Iterator<T> otherPathIt = otherPath.iterator();
				boolean matches = true;
				while(listIt.hasNext() && otherPathIt.hasNext() && matches){
					T thisNext = listIt.next();
					T otherNext = otherPathIt.next();
					if(!thisNext.equals(otherNext)){
						matches = false;
					}
				}
				// If we went through all points in the otherPath and they match then we can
				// say that this path contains otherPath.
				contains = !otherPathIt.hasNext() && matches;
			}
		}
		return contains;
	}

	public boolean contains(T t){
		return this.nodes.contains(t);
	}
	
	@Override
	public String toString() {
		return this.nodes.toString();
	}
	
	protected List<T> getNodes(){
		return this.nodes;
	}
	
	/**
	 * Returns the n-th node in this path.
	 * @param n integer between 0..(length() - 1) inclusive
	 * @return
	 * @throws IndexOutOfBoundsException if n < 0 or n >= length()
	 */
	public T getPoint(int n){
		return this.nodes.get(n);
	}
}
